﻿// Author:
//   Jason Couture, plaguethenet@gmail.com
//
// Copyright (c) 2007, Jason Couture
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
//    * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections.Generic;
using System.Text;

namespace dAmn
{
    /// <summary>
    /// Argument portion of the dAmn Packet
    /// </summary>
    public class PacketArgs
    {
        private string _body;
        private Dictionary<string, string> _args;

        /// <summary>
        /// Returns the named argument from the dAmn Packet
        /// </summary>
        /// <example>
        /// Args.args["e"] == "ok" ? "Success!" : "Error: " + Args.args["e"];
        /// </example>
        public Dictionary<string, string> args
        {
            get { return _args; }
            set { _args = value; }
        }
        /// <summary>
        /// Body of the parsed arguments sub-packet if any.
        /// </summary>
        public string body
        {
            get { return _body; }
            set { _body = value; }
        }
        /// <summary>
        /// Creates a new dAmnArgs class from a raw arguments sub-packet
        /// </summary>
        /// <param name="data">Raw Packet Data</param>
        /// <returns>New dAmnArgs instance containing the parsed information.</returns>
        public static PacketArgs getArgsNData(string data)
        {
            PacketArgs p = new PacketArgs();
            p.args = new Dictionary<string, string>();
            p.body = null;
            while (true)
            {
                if (data.Length == 0 || data[0] == '\n')
                    break;
                int i = data.IndexOf('\n');
                int j = data.IndexOf('=');
                if (j > i)
                {
                    j = data.IndexOf(':');
                    if (j > i)
                        break;
                }
                p.args[data.Substring(0, j)] = data.Substring(j + 1, i - (j + 1));
                data = data.Substring(i + 1);
            }
            if (data != null && data.Length > 0)
                p.body = data.Substring(1);
            else
                p.body = "";
            return p;
        }
    }

    /// <summary>
    /// Class representing a full dAmn protocol packet.
    /// </summary>
    public class Packet
    {
        protected string _cmd = "";
        protected string _param = "";
        protected string _raw = "";
        //private string _body = "";
        //private Dictionary<string, string> _args = new Dictionary<string,string>();
        internal PacketArgs argsAndBody;
        /// <summary>
        /// Convert the packet to a string, and store it in the body, then clear args, cmd and param.
        /// </summary>
        public void DumpToBody()
        {
            this.body = this;
            this.args.Clear();
            this.cmd = "";
            this.param = "";
        }
        /// <summary>
        /// Command name
        /// </summary>
        public string cmd
        {
            get { return _cmd; }
            set { _cmd = value; }
        }
        private Packet cachedPacket = null;
        private bool parseAttempted = false;
        public Packet SubPacket
        {
            get
            {
                if (!parseAttempted)
                {
                    if (string.IsNullOrEmpty(body.Trim()))
                    {
                        parseAttempted = true;
                        cachedPacket = null;
                    }
                    else
                    {
                        try
                        {
                            cachedPacket = Packet.parse(body);
                        }
                        catch
                        {
                            try
                            {
                                Packet p = new Packet();
                                p.argsAndBody = PacketArgs.getArgsNData(this.body);
                                p.cmd = "";
                                p.param = "";
                                cachedPacket = p;
                            }
                            catch { cachedPacket = null; }
                        }
                        parseAttempted = true;
                    }
                }
                return cachedPacket;
            }
            set
            {
                //Allow setting the sub packet, instead of using the body.
                cachedPacket = value;
                parseAttempted = true;
                this.body = value.ToString();
            }
        }
        /// <summary>
        /// Main Parameter.
        /// </summary>
        public string param
        {
            get { return _param; }
            set { _param = value; }
        }
        /// <summary>
        /// Unparsed body text.
        /// </summary>
        public string body
        {
            get { return argsAndBody.body; }
            set { argsAndBody.body = value; }
        }
        /// <summary>
        /// Returns the Raw dAmn packet as a string.
        /// </summary>
        public string raw
        {
            get { return _raw; }
            set { _raw = value; }
        }
        /// <summary>
        /// Arguments from the Argument sub-packet.
        /// </summary>
        public Dictionary<string, string> args
        {
            get { return argsAndBody.args; }
            set { argsAndBody.args = value; }
        }
        /// <summary>
        /// Un-parses a parsed packet back to a string. Arguments may not be in the original order.
        /// If the exact version of the packet is needed, Please use <seealso cref="raw">raw</seealso>.
        /// </summary>
        /// <returns>Unparsed version of the packet</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            //Removed space before line break on empty param - cthom
            //Do not print header at all if cmd is blank or null. - plaguethenet
            if (!string.IsNullOrEmpty(this.cmd)) sb.AppendFormat("{0}{1}{2}\n", _cmd, !string.IsNullOrEmpty(_param) ? " " : "", _param);
            if (this.argsAndBody.args.Count != 0 || !string.IsNullOrEmpty(this.argsAndBody.body))
            { 
                foreach (KeyValuePair<string, string> kv in args)
                {
                    sb.AppendFormat("{0}={1}\n", kv.Key, kv.Value);
                }
                sb.AppendFormat("\n{0}", this.body);
            }
            return sb.ToString();
        }
        public string ToString(bool rawformat)
        {
            string o = ToString();
            if (rawformat)
            {
                o = o.Replace("\n", "\\n").Replace("\t", "\\t");
            }
            return o;
        }
        public static Packet EmptyPacket
        {
            get
            {
                Packet p = new Packet();
                p.argsAndBody = PacketArgs.getArgsNData("\n\n");
                p.cmd = "";
                p.body = "";
                p.param = "";
                return p;
            }
        }
        /// <summary>
        /// Converts a raw packet into a new instance of the dAmnPacket class.
        /// </summary>
        /// <param name="data">Raw dAmn Packet</param>
        /// <returns>New Instance of dAmnPacket</returns>
        public static Packet parse(string data)
        {
            if (string.IsNullOrEmpty(data))
                throw new ArgumentNullException("data");

            try
            {
                Packet p = new Packet();
                int i = data.IndexOf('\n');
                if (i < 0)
                    throw new Exception("Parser error, No line break.");
                string tmp = data.Substring(0, i);
                p.cmd = tmp.Split(' ')[0];
                int j = tmp.IndexOf(' ');
                if (j > 0)
                    p.param = tmp.Substring(j + 1);
                if ((i + 1) >= data.Length)
                    p.argsAndBody = PacketArgs.getArgsNData("\n\n");
                else
                    p.argsAndBody = PacketArgs.getArgsNData(data.Substring(i + 1));
                return p;
            }
            catch (Exception e)
            {
                throw e;
            }
            //return null; //wtf? how did we get here? D:
        }
        public static bool TryParse(string input, out Packet output)
        {
            output = null;
            try
            {
                output = Packet.parse(input);
                return true;
            }
            catch
            {
                return false;
            }
        }
        // This allows a packet to be converted to a string
        // simply by trying to use it as a string.
        public static implicit operator string(Packet p)
        {
            return p.ToString();
        }
        //This allows strings to be parsed by casting them to a packet.
        //IE: p = (Packet)someString is the same as p = Packet.parse(someString);
        public static explicit operator Packet(string s)
        {
            return Packet.parse(s);
        }
    }

}
